﻿// HSS.Interlink.DownloadClient.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2012
// ----------------------------------------------------------------------------
// File:       DownloadClient.cs
// Author:     HSS\gbanta
// Created:    01/17/2012
// Modified:   01/17/2012
// ----------------------------------------------------------------------------
namespace HSS.Interlink
{
	#region Using Directives
	using System;
	using System.IO;
	using System.Net;
	using System.Threading;
	using System.ComponentModel;
	using System.Security;
	using System.Collections.ObjectModel;

#if SILVERLIGHT
	using System.Net.Browser;
	using System.Windows;
#endif

	#endregion

	#region DownloadClientAsyncContext
	internal sealed class DownloadClientAsyncContext
	{
		public DownloadClientAsyncContext(string fileNames, AsyncOperation asyncOperation, WebRequest request, Stream result)
		{
			this.FileNames = fileNames;
			this.Operation = asyncOperation;
			this.Request = request;
			this.Result = result;
		}
		internal string FileNames { get; set; }
		internal AsyncOperation Operation { get; set; }
		internal WebRequest Request { get; set; }
		internal Stream Result { get; set; }
	}
	#endregion

	#region EventHandlers
	/// <summary>
	/// Internal Download Async completion delegate.
	/// </summary>
	/// <param name="result"></param>
	/// <param name="exception"></param>
	/// <param name="asyncOp"></param>
	internal delegate void CompletionDelegate(Stream result, Exception exception, AsyncOperation asyncOp);
	/// <summary>
	/// A delegate for closing the result stream on the UI Thread.
	/// </summary>
	/// <param name="result"></param>
	internal delegate void CloseOnUI(Stream result);
	#endregion

	#region EventArgs

	#region DownloadFileProgressEventArgs
	/// <summary>
	/// Provides data for the <see cref="DownloadClient.DownloadProgressChanged"/> event of a <see cref="DownloadClient"/>.
	/// </summary>
	public sealed class DownloadFileProgressEventArgs : ProgressChangedEventArgs
	{
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="progressPercentage">The percentage of an asynchronous operation that has been completed.</param>
		/// <param name="userToken">The async operation user token.</param>
		/// <param name="bytesReceived">The number of bytes received.</param>
		/// <param name="totalBytesToReceive">The total number of bytes in a <see cref="DownloadClient"/> file download operation.</param>
		internal DownloadFileProgressEventArgs(int progressPercentage, object userToken, long bytesReceived, long totalBytesToReceive)
			: base(progressPercentage, userToken)
		{
			this.BytesReceived = bytesReceived;
			this.TotalBytesToReceive = totalBytesToReceive;
		}
		/// <summary>
		/// Gets the number of bytes received.
		/// </summary>
		/// <returns>An System.Int64 value that indicates the number of bytes received.</returns>
		public long BytesReceived { get; private set; }
		/// <summary>
		/// Gets the total number of bytes in a <see cref="DownloadClient"/> file download operation.
		/// </summary>
		/// <returns>An System.Int64 value that indicates the number of bytes that will be received.</returns>
		public long TotalBytesToReceive { get; private set; }
	}
	#endregion

	#region DownloadFileCompletedEventArgs
	/// <summary>
	/// Provides data for the DownloadClient DownloadCompleted event.
	/// </summary>
	public sealed class DownloadFileCompletedEventArgs : AsyncCompletedEventArgs
	{
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="result">The resulting Stream containing the download file.</param>
		/// <param name="exception">The exception if an error was encountered.</param>
		/// <param name="cancelled">true if the download was cancelled.</param>
		/// <param name="userToken">The user provided token.</param>
		internal DownloadFileCompletedEventArgs(Stream result, Exception exception, bool cancelled, object userToken)
			: base(exception, cancelled, userToken)
		{
			this._result = result;
		}
		/// <summary>
		/// Gets the originally provided stream, where the downloaded file was written to.
		/// </summary>
		public Stream Result
		{
			get
			{
				base.RaiseExceptionIfNecessary();
				return _result;
			}
		} Stream _result;
	}
	#endregion

	#endregion

	#region DownloadClient
	/// <summary>
	/// A service client for downloading remote files.
	/// </summary>
	public sealed class DownloadClient
	{
		#region Constants
		/// <summary>
		/// The default AppSetting Key for the FILE Handler (Default: 'DownloadHandler') and must match the key in the web.config.
		/// </summary>
		public const string DefaultHandlerKey = "DownloadHandler";
		/// <summary>
		/// The default URI for the HTTP Handler (Default: '/FileDownload.ashx') and must match the handler's path in the web.config
		/// </summary>
		public static readonly Uri DefaultDownloadUri = new Uri("/FileDownload.ashx", UriKind.Relative);
		/// <summary>
		/// Unknown DownloadClient exception encountered.
		/// </summary>
		private const string UnknownError = "Unknown DownloadClient exception encountered.";
		#endregion

		#region Events

		#region DownloadProgressChanged
		/// <summary>
		/// Occurs when an asynchronous download operation successfully transfers some or all of the data.
		/// </summary>
		public event EventHandler<DownloadFileProgressEventArgs> DownloadProgressChanged;
		private void ReportDownloadProgressChanged(DownloadFileProgressEventArgs args)
		{
			var handler = DownloadProgressChanged;
			if (null != handler)
				handler(null, args);
		}
		private void ReportDownloadProgressChanged(object arg)
		{
			this.ReportDownloadProgressChanged((DownloadFileProgressEventArgs)arg);
		}
		#endregion

		#region DownloadCompleted
		/// <summary>
		/// Occurs when an asynchronous download operation has completed.
		/// </summary>
		public event EventHandler<DownloadFileCompletedEventArgs> DownloadCompleted;
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void OnDownloadCompleted(DownloadFileCompletedEventArgs eventArgs)
		{
			var handler = this.DownloadCompleted;
			if (null != handler)
			{
				try { handler(this, eventArgs); }
				catch { }
			}
		}
		private void OnDownloadAsyncCompleted(object arg)
		{
			this.OnDownloadCompleted((DownloadFileCompletedEventArgs)arg);
		}
		private void InvokeOperationCompeted(AsyncOperation asyncOp, SendOrPostCallback callback, DownloadFileCompletedEventArgs eventArgs)
		{
			if (Interlocked.CompareExchange<AsyncOperation>(ref this.asyncOperation, null, asyncOp) == asyncOp)
			{
				this.CompleteDownloadClientState();
				asyncOp.PostOperationCompleted(callback, eventArgs);
			}
		}
		private void CompleteDownloadClientState()
		{
			Interlocked.Decrement(ref this.callNesting);
		}
		#endregion

		#endregion

		#region Fields

		private Guid jobId;
		private Uri resolvedUri;
		private bool multipleFiles;
		private string fileNames;
		private bool autoCloseStream;
		private bool autoResetPosition;

		private ProgressData progressData;
		private WebRequest webRequest;
		private WebResponse webResponse;
		private SendOrPostCallback downloadOperationCompleted;
		private SendOrPostCallback downloadProgressChanged;
		private AsyncOperation asyncOperation;
		private bool initWebClientAsync;
		private int callNesting;

#if SILVERLIGHT
		private bool isRunningOutOfBrowser;
#endif

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// <para>
		/// NOTE: The constructor must be called from the UI Thread. The call to <see cref="DownloadAsync(string[])"/> and it's overrides
		/// does not.
		/// </para>
		/// </remarks>
		public DownloadClient()
			: this(DefaultHandlerKey, DefaultDownloadUri)
		{
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="handlerKey">The appsetting key of your file handler.</param>
		/// <param name="downloadUri">The uri to the configured HSS Interlink FileDownload ashx handler.</param>
		/// <remarks>
		/// <para>
		/// NOTE: The constructor must be called from the UI Thread. The call to <see cref="DownloadAsync(string[])"/> and it's overrides
		/// does not.
		/// </para>
		/// </remarks>
		public DownloadClient(string handlerKey, string downloadUri)
			: this(handlerKey, new Uri(downloadUri, UriKind.RelativeOrAbsolute))
		{
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="handlerKey">The appsetting key of your file handler.</param>
		/// <param name="downloadUri">The uri to the configured HSS Interlink FileDownload ashx handler.</param>
		/// <remarks>
		/// <para>
		/// NOTE: The constructor must be called from the UI Thread. The call to <see cref="DownloadAsync(string[])"/> and it's overrides
		/// does not.
		/// </para>
		/// </remarks>
		public DownloadClient(string handlerKey, Uri downloadUri)
		{
#if SILVERLIGHT
			if (!Deployment.Current.Dispatcher.CheckAccess())
				throw new UnauthorizedAccessException("Invalid cross-thread access.");
			this.isRunningOutOfBrowser = Application.Current.IsRunningOutOfBrowser;
#endif
			if (string.IsNullOrWhiteSpace(handlerKey))
				throw new ArgumentNullException("handlerKey");

			if (null == downloadUri)
				throw new ArgumentNullException("downloadUri");

			this.HandlerKey = handlerKey;
			this.DownloadUri = downloadUri.OriginalString;
			this.resolvedUri = FileQuery.ResolveUrl(this.DownloadUri);
		}
		#endregion

		#region Properties

		#region Read-Only
		/// <summary>
		/// Gets the name of the file the downloaded contents will be saved to.
		/// </summary>
		public string LocalFileName
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the originally requested remote files.
		/// </summary>
		public ReadOnlyCollection<string> RemoteFiles
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the uri of the http handler.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
		public string DownloadUri
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets if the download is currently in progress.
		/// </summary>
		public bool IsBusy
		{
			get
			{
				return (this.asyncOperation != null);
			}
		}
		/// <summary>
		/// Gets if the download process was cancelled.
		/// </summary>
		public bool IsCanceled
		{
			get { return _isCanceled; }
		} private bool _isCanceled;
		#endregion

		#region Read/Write
		/// <summary>
		/// Gets or sets the network credentials that are sent to the host and used to authenticate the request.
		/// </summary>
		/// <value>An <see cref="ICredentials"/> containing the authentication credentials for the request. The default is null.</value>
		public ICredentials Credentials
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets a <see cref="Boolean"/> value that controls whether default credentials are sent with requests.
		/// </summary>
		/// <value>true if the default credentials are used; otherwise false. The default value is true.</value>
		public bool UseDefaultCredentials
		{
			get
			{
				return this._useDefaultCredentials;
			}
			set
			{
				this._useDefaultCredentials = value;
				this._userSetUseDefaultCredentials = true;
			}
		} bool _useDefaultCredentials = true; bool _userSetUseDefaultCredentials;
		/// <summary>
		/// Gets or sets the web.config AppSetting Key that specifies the File Handler
		/// for this file download request.
		/// </summary>
		/// <remarks>Cannot be modified once the download process has begun.</remarks>
		public string HandlerKey
		{
			get { return _handlerKey; }
			set
			{
				if (this.IsBusy)
					throw new WebException("Cannot modify the HandlerKey once the download has begun.");
				if (string.IsNullOrWhiteSpace(value))
					throw new InvalidOperationException("Cannot set the HandlerKey to null or an empty string.");
				_handlerKey = value;
			}
		} string _handlerKey;
		#endregion

		#endregion

		#region Download

		#region Public
		/// <summary>
		/// Begins downloading the requested remote file(s).
		/// </summary>
		/// <param name="remoteFiles">The array of Names or Ids or Keys of the files to be downloaded.</param>
		/// <remarks>
		/// <para>
		/// NOTE: This method will create and fill a new <see cref="MemoryStream"/> with the downloaded contents and
		/// is made available from the Result property of the Completed event arguments.
		/// </para>
		/// For more options see the other DownloadAsync overrides.
		/// </remarks>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
		public void DownloadAsync(params string[] remoteFiles)
		{
			this.DownloadAsync(new MemoryStream(), false, true, string.Empty, null, remoteFiles);
		}
		/// <summary>
		/// Begins downloading the requested remote file(s) to the provide result stream.
		/// </summary>
		/// <param name="resultStream">The stream to write the the downloaded contents to.</param>
		/// <param name="remoteFiles">The array of Names or Ids or Keys of the files to be downloaded.</param>
		/// <remarks>
		/// <para>
		/// NOTE: The provided result stream will be closed once the download has completed and is typical
		/// behavior when saving to a local file stream provided from the <c>System.Windows.Controls.SaveFileDialog.OpenFile()</c>.
		/// </para>
		/// For more options see the other DownloadAsync overrides.
		/// </remarks>
		public void DownloadAsync(Stream resultStream, params string[] remoteFiles)
		{
			this.DownloadAsync(resultStream, true, false, string.Empty, null, remoteFiles);
		}
		/// <summary>
		/// Begins downloading the requested remote file(s) to the provided result stream.
		/// </summary>
		/// <param name="resultStream">The stream to save the the downloaded contents to.</param>
		/// <param name="closeStream">true to close the provided result stream once the download completes; otherwise false.</param>
		/// <param name="resetPosition">true to set the provided result stream's position to zero, once the download completes; otherwise false.</param>
		/// <param name="localFileName">The optional name of the local file, the downloaded contents is being saved to.</param>
		/// <param name="userToken">The optional user token to track this operation.</param>
		/// <param name="remoteFiles">The array of Names or Ids or Keys of the files to be downloaded.</param>
		/// <remarks>
		/// <para>
		/// Does not alter the current position of the provided stream prior to writing, allowing you to append the downloaded contents to it.
		/// </para>
		/// <para>
		/// Set the <paramref name="closeStream"/> to false to NOT close the provided result stream once the 
		/// download completes. This is typical if you want to further manipulate the downloaded contents. In this
		/// scenario you'd typically want to set the <paramref name="resetPosition"/> to true unless you're
		/// appending to the provided stream and want to leave the position at the end of the stream for futher
		/// manipulatation.
		/// </para>
		/// </remarks>
		public void DownloadAsync(Stream resultStream, bool closeStream, bool resetPosition, string localFileName, object userToken, params string[] remoteFiles)
		{
			if (null == resultStream)
				throw new ArgumentNullException("resultStream");

			if (!resultStream.CanWrite)
				throw new ArgumentException("The provided resultStream is not writable.");

			if (null == remoteFiles)
				throw new ArgumentNullException("remoteFiles");

			if (remoteFiles.Length == 0)
				throw new ArgumentOutOfRangeException("remoteFiles");

			this.InitDownloadClientState();
			this.ClearDownloadClientState();

			this.LocalFileName = localFileName;
			this.RemoteFiles = new ReadOnlyCollection<string>(remoteFiles);

			this.autoCloseStream = closeStream;
			this.autoResetPosition = resetPosition;

			this.jobId = Guid.NewGuid();
			this.multipleFiles = remoteFiles.Length > 1;
			this.fileNames = string.Join(",", remoteFiles);

			var asyncOp = AsyncOperationManager.CreateOperation(userToken);
			this.asyncOperation = asyncOp;

			try
			{
				var fileQuery = FileQuery.CreateDownloadFileQuery(this.HandlerKey, this.jobId, this.LocalFileName, FileQueryCommand.Prepare);
				var req = this.GetWebRequest(Strings.HTTP_POST, fileQuery);
				var asyncContext = new DownloadClientAsyncContext(this.fileNames, this.asyncOperation, req, resultStream);
				req.BeginGetRequestStream(DownloadAsyncPrepareCallback, asyncContext);
			}
			catch (Exception exception)
			{
				if (((exception is ThreadAbortException) || (exception is StackOverflowException)) || (exception is OutOfMemoryException))
				{
					throw;
				}
				if (!(exception is WebException) && !(exception is SecurityException))
				{
					exception = new WebException(UnknownError, exception);
				}
				var eventArgs = new DownloadFileCompletedEventArgs(null, exception, this.IsCanceled, asyncOp.UserSuppliedState);
				this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
			}
		}
		/// <summary>
		/// Attempts to cancel the download request.
		/// </summary>
		public void CancelAsync()
		{
			var req = this.webRequest;
			var res = this.webResponse;
			this._isCanceled = true;
			AbortRequest(req);
			CloseResponse(res);
		}
		#endregion

		#region Private

		private void InitDownloadClientState()
		{
			if (!this.initWebClientAsync)
			{
				this.downloadOperationCompleted = new SendOrPostCallback(this.OnDownloadAsyncCompleted);
				this.downloadProgressChanged = new SendOrPostCallback(this.ReportDownloadProgressChanged);
				this.progressData = new ProgressData();
				this.initWebClientAsync = true;
			}
		}
		private void ClearDownloadClientState()
		{
			if (AnotherCallInProgress(Interlocked.Increment(ref this.callNesting)))
			{
				this.CompleteDownloadClientState();
				throw new NotSupportedException("Concurrent download requests are not supported.");
			}
			this.webRequest = null;
			this.webResponse = null;
			this._isCanceled = false;
			if (this.progressData != null)
				this.progressData.Reset();
		}
		private static bool AnotherCallInProgress(int nesting)
		{
			return (nesting > 1);
		}

		private void DownloadAsyncPrepareCallback(IAsyncResult result)
		{
			var asyncContext = result.AsyncState as DownloadClientAsyncContext;
			var asyncOp = asyncContext.Operation;
			var req = asyncContext.Request;
			var exception = default(Exception);
			var requestStream = default(Stream);

			try
			{
				requestStream = req.EndGetRequestStream(result);
			}
			catch (Exception exception2)
			{
				#region Handler Exceptions
				if (((exception2 is ThreadAbortException) || (exception2 is StackOverflowException)) || (exception2 is OutOfMemoryException))
				{
					throw;
				}
				exception = exception2;
				var wex = exception2 as WebException;
				var sex = exception2 as SecurityException;
				if (null == wex && null == sex)
				{
					exception = new WebException(UnknownError, exception2);
				}
				if (null != wex)
				{
					if (wex.Status == WebExceptionStatus.RequestCanceled)
					{
						exception = null;
						this._isCanceled = true;
					}
				}
				#endregion
			}

			if (null != exception || this.IsCanceled || null == requestStream)
			{
				#region Done
				if (null != requestStream)
					requestStream.Close();
				req.Abort();
				if (!this.IsCanceled && null == exception)
					exception = new WebException(UnknownError);
				var eventArgs = new DownloadFileCompletedEventArgs(null, exception, this.IsCanceled, asyncOp.UserSuppliedState);
				this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
				#endregion
				return;
			}

			try
			{
				#region Write request
				var bytes = System.Text.Encoding.UTF8.GetBytes(asyncContext.FileNames);
				requestStream.Write(bytes, 0, bytes.Length);
				requestStream.Close();
				requestStream = null;
				#endregion

				req.BeginGetResponse(DownloadAsyncPrepareResponseCallback, asyncContext);
			}
			catch (Exception exception2)
			{
				#region Handle Exceptions

				if (null != requestStream)
					requestStream.Close();

				if (((exception2 is ThreadAbortException) || (exception2 is StackOverflowException)) || (exception2 is OutOfMemoryException))
				{
					throw;
				}
				exception = exception2;
				var wex = exception2 as WebException;
				var sex = exception2 as SecurityException;
				if (null == wex && null == sex)
				{
					exception = new WebException(UnknownError, exception2);
				}
				if (null != wex)
				{
					if (wex.Status == WebExceptionStatus.RequestCanceled)
					{
						exception = null;
						this._isCanceled = true;
					}
				}
				#endregion

				#region Done
				var eventArgs = new DownloadFileCompletedEventArgs(null, exception, this.IsCanceled, asyncOp.UserSuppliedState);
				this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
				#endregion
			}
		}
		private void DownloadAsyncPrepareResponseCallback(IAsyncResult result)
		{
			var asyncState = (DownloadClientAsyncContext)result.AsyncState;
			var asyncOp = asyncState.Operation;
			var request = asyncState.Request;
			var responseStream = default(Stream);
			var response = string.Empty;
			var exception = default(Exception);
			try
			{
				responseStream = this.GetWebResponse(request, result).GetResponseStream();
				using (var reader = new StreamReader(responseStream))
					response = reader.ReadToEnd();
				if (response.Length > 0)
					throw new WebException(response);
				if (this._isCanceled)
				{
					var eventArgs = new DownloadFileCompletedEventArgs(null, exception, this.IsCanceled, asyncOp.UserSuppliedState);
					this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
					return;
				}
				this.DownloadAsyncDownload(asyncState);
			}
			catch (Exception exception2)
			{
				#region Handle Exceptions
				if (((exception2 is ThreadAbortException) || (exception2 is StackOverflowException)) || (exception2 is OutOfMemoryException))
				{
					throw;
				}
				request.Abort();
				exception = exception2;
				var wex = exception2 as WebException;
				var sex = exception2 as SecurityException;
				if (null == wex && null == sex)
				{
					exception = new WebException(UnknownError, exception2);
				}
				if (null != wex)
				{
					if (wex.Status == WebExceptionStatus.RequestCanceled)
					{
						exception = null;
						this._isCanceled = true;
					}
				}
				#endregion

				var eventArgs = new DownloadFileCompletedEventArgs(null, exception, this.IsCanceled, asyncOp.UserSuppliedState);
				this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
			}
		}
		private void DownloadAsyncDownload(DownloadClientAsyncContext asyncState)
		{
			var asyncOp = asyncState.Operation;
			try
			{
				var fileQuery = FileQuery.CreateDownloadFileQuery(this.HandlerKey, this.jobId, this.LocalFileName, this.multipleFiles ? FileQueryCommand.GetMultipleFiles : FileQueryCommand.GetSingleFile);
				var req = this.GetWebRequest(Strings.HTTP_GET, fileQuery, false);
				asyncState.Request = req;
				this.DownloadFile(req, asyncState.Result, new CompletionDelegate(this.DownloadAsyncDownloadCallback), asyncOp);
			}
			catch (Exception exception)
			{
				if (((exception is ThreadAbortException) || (exception is StackOverflowException)) || (exception is OutOfMemoryException))
					throw;
				if (!(exception is WebException) && !(exception is SecurityException))
					exception = new WebException(UnknownError, exception);
				this.DownloadAsyncDownloadCallback(asyncState.Result, exception, asyncOp);
			}
		}
		private void DownloadAsyncDownloadCallback(Stream result, Exception exception, AsyncOperation asyncOp)
		{
			var eventArgs = new DownloadFileCompletedEventArgs(result, exception, this.IsCanceled, asyncOp.UserSuppliedState);
			this.InvokeOperationCompeted(asyncOp, this.downloadOperationCompleted, eventArgs);
		}
		private byte[] DownloadFile(WebRequest request, Stream writeStream, CompletionDelegate completionDelegate, AsyncOperation asyncOp)
		{
			var state = new DownloadFileState(request, writeStream, completionDelegate, asyncOp, this.progressData, this);
			request.BeginGetResponse(new AsyncCallback(DownloadClient.DownloadFileResponseCallback), state);
			return null;
		}
		private static void DownloadFileResponseCallback(IAsyncResult result)
		{
			var asyncState = (DownloadFileState)result.AsyncState;
			var request = asyncState.Request;
			var response = default(WebResponse);
			var exception = default(Exception);
			try
			{
				response = asyncState.Client.GetWebResponse(request, result);
				asyncState.Client.webResponse = response;
				asyncState.SetResponse(response);
			}
			catch (Exception exception2)
			{
				if (((exception2 is ThreadAbortException) || (exception2 is StackOverflowException)) || (exception2 is OutOfMemoryException))
					throw;
				exception = exception2;
				if (!(exception2 is WebException) && !(exception2 is SecurityException))
					exception = new WebException(UnknownError, exception2);
				AbortRequest(request);
				if (asyncState != null)
					asyncState.Close();
			}
			finally
			{
				if (exception != null && asyncState != null)
					asyncState.CompletionDelegate(null, exception, asyncState.AsyncOp);
			}
		}

		private static void AbortRequest(WebRequest request)
		{
			try
			{
				if (request != null)
					request.Abort();
			}
			catch (Exception exception)
			{
				if (((exception is OutOfMemoryException) || (exception is StackOverflowException)) || (exception is ThreadAbortException))
				{
					throw;
				}
			}
		}
		private static void CloseResponse(WebResponse response)
		{
			try
			{
				if (response != null)
					response.Close();
			}
			catch (Exception exception)
			{
				if (((exception is OutOfMemoryException) || (exception is StackOverflowException)) || (exception is ThreadAbortException))
				{
					throw;
				}
			}
		}
		private static void DownloadFileReadCallbackState(DownloadFileState state, IAsyncResult result)
		{
			var readStream = state.ReadStream;
			var exception = default(Exception);
			bool isDone = false;
			try
			{
				var bytesRetrieved = 0;
				if (readStream != null && readStream != Stream.Null)
					bytesRetrieved = readStream.EndRead(result);
				isDone = state.RetrieveBytes(ref bytesRetrieved);
			}
			catch (Exception exception2)
			{
				isDone = true;
				if (!state.IsCanceled)
				{
					if (((exception2 is ThreadAbortException) || (exception2 is StackOverflowException)) || (exception2 is OutOfMemoryException))
						throw;
					exception = exception2;
					if (!(exception2 is WebException) && !(exception2 is SecurityException))
						exception = new WebException(UnknownError, exception2);
					AbortRequest(state.Request);
					if ((state != null))
						state.CloseWriteStream(true);
				}
			}
			finally
			{
				if (isDone)
				{
					if (exception == null)
						state.Close();
					state.CompletionDelegate(state.WriteStream, exception, state.AsyncOp);
				}
			}
		}
		private static void DownloadFileReadCallback(IAsyncResult result)
		{
			var asyncState = (DownloadFileState)result.AsyncState;
			DownloadFileReadCallbackState(asyncState, result);
		}

#if SILVERLIGHT
		private HttpWebRequest GetWebRequest(string requestMethod, string query, bool allowReadStreamBuffering = true, bool allowWriteStreamBuffering = true)
		{
			var uri = new Uri(resolvedUri.ToString() + query);
			var request = default(HttpWebRequest);
			if (this.isRunningOutOfBrowser)
				request = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(uri);
			else
				request = (HttpWebRequest)WebRequestCreator.BrowserHttp.Create(uri);

			if (this._userSetUseDefaultCredentials)
				request.UseDefaultCredentials = this._useDefaultCredentials;
			request.AllowReadStreamBuffering = allowReadStreamBuffering;
			request.AllowWriteStreamBuffering = allowWriteStreamBuffering;
			if (this.Credentials != null)
				request.Credentials = this.Credentials;
			request.Method = requestMethod.ToUpperInvariant();

			var originalWebRequest = Interlocked.Exchange<WebRequest>(ref this.webRequest, request);
			if (null != originalWebRequest)
			{
				originalWebRequest.Abort();
				originalWebRequest = null;
			}

			return request;
		}
#else
		private HttpWebRequest GetWebRequest(string requestMethod, string query, bool allowWriteStreamBuffering = true)
		{
			var uri = new Uri(resolvedUri.ToString() + query);
			var request = (HttpWebRequest)WebRequest.Create(uri);
			if (this._userSetUseDefaultCredentials)
				request.UseDefaultCredentials = this._useDefaultCredentials;
			request.AllowWriteStreamBuffering = allowWriteStreamBuffering;
			if (this.Credentials != null)
				request.Credentials = this.Credentials;
			request.Method = requestMethod.ToUpperInvariant();

			var originalWebRequest = Interlocked.Exchange<WebRequest>(ref this.webRequest, request);
			if (null != originalWebRequest)
			{
				originalWebRequest.Abort();
				originalWebRequest = null;
			}

			return request;
		}
#endif
		private WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
		{
			var response = request.EndGetResponse(result);
			var originalWebResponse = Interlocked.Exchange<WebResponse>(ref this.webResponse, response);
			if (null != originalWebResponse)
			{
				originalWebResponse.Close();
				originalWebResponse = null;
			}
			return response;
		}
		private void PostProgressChanged(AsyncOperation asyncOp, ProgressData progress)
		{
			if (asyncOp != null)
			{
				var pct = progress.TotalBytesToReceive > 0 ? ((int)((100L * progressData.BytesReceived) / progressData.TotalBytesToReceive)) : 0;

				asyncOp.Post(this.downloadProgressChanged,
					new DownloadFileProgressEventArgs(pct,
						asyncOp.UserSuppliedState,
						progress.BytesReceived,
						progress.TotalBytesToReceive));
			}
		}

		#endregion

		#endregion

		#region Open

		#region Inline
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileInline(string remoteFile)
		{
			OpenFileInline(DefaultDownloadUri.OriginalString, DefaultHandlerKey, remoteFile);
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
		public static void OpenFileInline(string uri, string remoteFile)
		{
			OpenFile(uri, DefaultHandlerKey, remoteFile, FileQueryCommand.OpenFileInline);
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
		public static void OpenFileInline(string uri, string handlerKey, string remoteFile)
		{
			OpenFile(uri, handlerKey, remoteFile, FileQueryCommand.OpenFileInline);
		}
		#endregion

		#region Attachment
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileAsAttachment(string remoteFile)
		{
			OpenFileAsAttachment(DefaultDownloadUri.OriginalString, DefaultHandlerKey, remoteFile);
		}
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
		public static void OpenFileAsAttachment(string uri, string remoteFile)
		{
			OpenFile(uri, DefaultHandlerKey, remoteFile, FileQueryCommand.OpenFileAsAttachment);
		}
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
		public static void OpenFileAsAttachment(string uri, string handlerKey, string remoteFile)
		{
			OpenFile(uri, handlerKey, remoteFile, FileQueryCommand.OpenFileAsAttachment);
		}
		#endregion

		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="remoteFile">The Name or Id or Key of the file to be download.</param>
		/// <param name="command">The FileQueryCommand.</param>
		private static void OpenFile(string uri, string handlerKey, string remoteFile, FileQueryCommand command)
		{
			if (string.IsNullOrEmpty(remoteFile))
				throw new ArgumentNullException(remoteFile);
			var resolvedUri = FileQuery.ResolveUrl(uri);
			var query = FileQuery.CreateDownloadFileQuery(handlerKey, Guid.NewGuid(), remoteFile, command);
			var fullUri = new Uri(resolvedUri.ToString() + query);
#if SILVERLIGHT
			SafeNavigator.Navigate(fullUri, true);
#else
			SafeNavigator.Navigate(fullUri);
#endif
		}

		#endregion

		#region ProgressData
		private class ProgressData
		{
			internal long BytesReceived;
			internal long TotalBytesToReceive = -1L;

			internal void Reset()
			{
				this.BytesReceived = 0L;
				this.TotalBytesToReceive = -1L;
			}
		}
		#endregion

		#region DownloadFileState
		private class DownloadFileState
		{
			#region Constants
			internal const string saveFileStreamType = "System.Windows.SaveFileStream";
			#endregion

			#region Fields

			internal AsyncOperation AsyncOp;
			internal CompletionDelegate CompletionDelegate;
			internal DownloadClient.ProgressData Progress;
			internal DownloadClient Client;
			internal WebRequest Request;
			internal Stream ReadStream;
			internal Stream WriteStream;
			internal long ContentLength;
			internal bool isClosed;

			private byte[] buffer;
			private int bufferSize = 0x10000; // 64KB
			private bool AutoCloseResult;
			private bool AutoResetResultPosition;

#if SILVERLIGHT
			private string resultStreamType;
			private CloseOnUI closeOnUIThread;
#endif

			#endregion

			#region Constructors
			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="request"></param>
			/// <param name="writeStream"></param>
			/// <param name="completionDelegate"></param>
			/// <param name="asyncOp"></param>
			/// <param name="progress"></param>
			/// <param name="client"></param>
			internal DownloadFileState(WebRequest request, Stream writeStream, CompletionDelegate completionDelegate, AsyncOperation asyncOp, DownloadClient.ProgressData progress, DownloadClient client)
			{
				this.WriteStream = writeStream;
				this.Request = request;
				this.AsyncOp = asyncOp;
				this.CompletionDelegate = completionDelegate;
				this.Client = client;
				this.Progress = progress;
				this.AutoCloseResult = client.autoCloseStream;
				this.AutoResetResultPosition = client.autoResetPosition;
#if SILVERLIGHT
				this.resultStreamType = writeStream.GetType().FullName;
				this.closeOnUIThread = new CloseOnUI(this.CloseResultStreamOnUI);
#endif
			}
			#endregion

			#region Properties
			internal bool Async
			{
				get { return (this.AsyncOp != null); }
			}
			internal bool IsCanceled
			{
				get
				{
					return this.Client._isCanceled;
				}
			}
			#endregion

			#region Methods
			internal void Close()
			{
				if (isClosed)
					return;
				isClosed = true;
				if (this.ReadStream != null)
					this.ReadStream.Close();
				this.CloseWriteStream();
			}
			internal void CloseWriteStream(bool forceClose = false)
			{
				if (null != this.WriteStream)
				{
					if (this.AutoCloseResult || forceClose)
					{
#if SILVERLIGHT
						if (resultStreamType == saveFileStreamType)
						{
							Deployment.Current.Dispatcher.BeginInvoke(this.closeOnUIThread, this.WriteStream);
						}
						else
						{
							this.WriteStream.Close();
						}
#else
						this.WriteStream.Close();
#endif
					}
					else if (this.AutoResetResultPosition && this.WriteStream.CanSeek)
					{
						this.WriteStream.Position = 0;
					}
				}
			}
			internal int SetResponse(WebResponse response)
			{
				this.ContentLength = response.ContentLength;
				if (this.ContentLength < 1)
					throw new WebException("File length is zero (0). Nothing to download.", WebExceptionStatus.MessageLengthLimitExceeded);
				if (this.ContentLength > 0x7fffffffL)
					throw new WebException("The requested file is too large to be downloaded.", WebExceptionStatus.MessageLengthLimitExceeded);
				this.buffer = new byte[bufferSize];
				this.ReadStream = response.GetResponseStream();
				if (response.ContentLength >= 0L)
					this.Progress.TotalBytesToReceive = response.ContentLength;
				this.ReadStream.BeginRead(this.buffer, 0, bufferSize, new AsyncCallback(DownloadClient.DownloadFileReadCallback), this);
				return -1;
			}
			internal bool RetrieveBytes(ref int bytesRetrieved)
			{
				if (bytesRetrieved > 0)
				{
					this.WriteStream.Write(this.buffer, 0, bytesRetrieved);
					if (this.Async)
					{
						this.Progress.BytesReceived += (long)bytesRetrieved;
						this.Client.PostProgressChanged(this.AsyncOp, this.Progress);
					}
					if (this.Progress.BytesReceived != this.ContentLength)
					{
						if (this.Async && !this.IsCanceled)
						{
							this.ReadStream.BeginRead(this.buffer, 0, bufferSize, new AsyncCallback(DownloadClient.DownloadFileReadCallback), this);
							return false;
						}
					}
				}
				if (this.Async)
				{
					if (this.Progress.TotalBytesToReceive < 0L)
						this.Progress.TotalBytesToReceive = this.Progress.BytesReceived;
					this.Client.PostProgressChanged(this.AsyncOp, this.Progress); // Final Progress
				}
				this.Close();
				return true;
			}
#if SILVERLIGHT
			private void CloseResultStreamOnUI(Stream result)
			{
				result.Close();
			}
#endif
			#endregion
		}
		#endregion
	}
	#endregion
}